<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Controller
 * @subpackage Dispatcher
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/** Zend_Loader */
require_once 'Framework/Loader.php';

/** Zend_Controller_Dispatcher_Abstract */
require_once 'Framework/Controller/Dispatcher/Abstract.php';

/** Zend_Controller_Request_Abstract */
require_once 'Framework/Controller/Request/Abstract.php';

/** Zend_Controller_Response_Abstract */
require_once 'Framework/Controller/Response/Abstract.php';

/** Zend_Controller_Action */
require_once 'Framework/Controller/Action.php';

/**
 * @category   Zend
 * @package    Zend_Controller
 * @subpackage Dispatcher
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Framework_Controller_Dispatcher_Standard extends Framework_Controller_Dispatcher_Abstract
{
    /**
     * Controller directory(ies)
     * @var array
     */
    protected $_controllerDirectory = array();

    /**
     * Constructor: Set current module to default value
     *
     * @param  array $params
     * @return void
     */
    public function __construct()
    {}

    /**
     * Set controller directory
     *
     * @param array|string $directory
     * @return Zend_Controller_Dispatcher_Standard
     */
    public function setControllerDirectory($directory)
    {
        if (is_string($directory)) {
            $this->_controllerDirectory = rtrim((string) $directory, '/\\');
        } else {
            throw new Framework_Controller_Exception('Controller directory spec must be either a string or an array');
        }

        return $this;
    }

    /**
     * Return the currently set directories for Framework_Controller_Action class
     * lookup
     *
     * If a module is specified, returns just that directory.
     *
     * @param  string $module Module name
     * @return array|string Returns array of all directories by default, single
     * module directory if module argument provided
     */
    public function getControllerDirectory()
    {
        return $this->_controllerDirectory;
    }

    /**
     * Convert a class name to a filename
     *
     * @param string $class
     * @return string
     */
    public function classToFilename($class)
    {
        return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
    }

    /**
     * Returns TRUE if the Framework_Controller_Request_Abstract object can be
     * dispatched to a controller.
     *
     * Use this method wisely. By default, the dispatcher will fall back to the
     * default controller (either in the module specified or the global default)
     * if a given controller does not exist. This method returning false does
     * not necessarily indicate the dispatcher will not still dispatch the call.
     *
     * @param Framework_Controller_Request_Abstract $action
     * @return boolean
     */
    public function isDispatchable(Framework_Controller_Request_Abstract $request)
    {
        $className = $this->formatControllerName($request->getControllerName());
        
        if (!$className) {
            return false;
        }

        if (class_exists($className, false)) {
            return true;
        }

        $fileSpec    = $this->classToFilename($className);
        
        // Меняем путь к контроллерам (. /Admin/)
        if ($request->has('admin')) {
            $this->setControllerDirectory(dirname($this->getControllerDirectory()) . '/admin');
        }
        
        $dispatchDir = $this->getControllerDirectory();
        $test        = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
        
        return Framework_Loader::isReadable($test);
    }

    /**
     * Dispatch to a controller/action
     *
     * By default, if a controller is not dispatchable, dispatch() will throw
     * an exception. If you wish to use the default controller instead, set the
     * param 'useDefaultControllerAlways' via {@link setParam()}.
     *
     * @param Framework_Controller_Request_Abstract $request
     * @param Framework_Controller_Response_Abstract $response
     * @return boolean
     * @throws Framework_Controller_Dispatcher_Exception
     */
    public function dispatch(Framework_Controller_Request_Abstract $request, Framework_Controller_Response_Abstract $response)
    {
        /**
         * Get controller class
         */
        if (!$this->isDispatchable($request)) {
            require_once 'Framework/Controller/Dispatcher/Exception.php';
            throw new Framework_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
        }
        
        $className = $this->formatControllerName($request->getControllerName());

        /**
         * Load the controller class file
         */
        $className = $this->loadClass($className);

        /**
         * Instantiate controller with request, response, and invocation
         * arguments; throw exception if it's not an action controller
         */
        $controller = new $className($request, $response);
        
        if (!$controller instanceof Framework_Controller_Action) {
            require_once 'Framework/Controller/Dispatcher/Exception.php';
            throw new Framework_Controller_Dispatcher_Exception("Controller '$className' is not an instance of Framework_Controller_Action");
        }

        /**
         * Retrieve the action name
         */
        $action = $this->formatActionName($request->getActionName());

        $controller->dispatch($action);
    }

    /**
     * Load a controller class
     *
     * Attempts to load the controller class file from
     * {@link getControllerDirectory()}.  If the controller belongs to a
     * module, looks for the module prefix to the controller class.
     *
     * @param string $className
     * @return string Class name loaded
     * @throws Framework_Controller_Dispatcher_Exception if class not loaded
     */
    public function loadClass($className)
    {
        if (class_exists($className, false)) {
            return $className;
        }

        $dispatchDir = $this->getControllerDirectory();
        $loadFile    = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
        $dir         = dirname($loadFile);
        $file        = basename($loadFile);

        try {
            Framework_Loader::loadFile($file, $dir, true);
        } catch (Framework_Exception $e) {
            require_once 'Framework/Controller/Dispatcher/Exception.php';
            throw new Framework_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $file . '" in directory "' . $dir . '"');
        }

        if (!class_exists($className, false)) {
            require_once 'Framework/Controller/Dispatcher/Exception.php';
            throw new Framework_Controller_Dispatcher_Exception('Invalid controller class ("' . $className . '")');
        }

        return $className;
    }
}
